{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4f43e2d",
   "metadata": {},
   "outputs": [],
   "source": [
    "add = lambda x,y:x+y"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80470195",
   "metadata": {},
   "source": [
    "## 7.1 基础知识\n",
    "### 7.1.1 函数参数的常用技巧\n",
    "1. 被将可变类型作为默认参数值\n",
    "- python函数的参数默认值只会在函数定义阶段被创建一次\n",
    "2. 定义特殊对象来区分是否提供了默认参数 \n",
    "- 无法严格区分调用方是不是真的提供了这个默认参数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9a290166",
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义标记变量\n",
    "#object 通常不会单独使用，但是拿来做这种标记变量刚刚好\n",
    "_not_set = object()\n",
    "\n",
    "def dump_value(value, extra = _not_set):\n",
    "    if extra is _not_set:\n",
    "        #调用方没有传递extra参数\n",
    "        \"\"\"\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7190a8a0",
   "metadata": {},
   "source": [
    "3. 定义仅限关键字参数\n",
    "- 函数接受的参数不要太多，最好不要超过3个\n",
    "- \"仅限关键字参数\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3bda9bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "#注意参数列表里的*号\n",
    "def query_users(limit, offset, *, min_followers_count, include_profile):\n",
    "    \"\"\" \"\"\"\n",
    "query_users(20, 0, min_followers_count = 100, include_profile=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea633352",
   "metadata": {},
   "source": [
    "- “仅限位置参数” "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8545a6f",
   "metadata": {},
   "source": [
    "### 7.1.2 函数返回的常见模式\n",
    "1. 尽量只返回一种类型\n",
    "2. 谨慎返回None值\n",
    "   - 返回None的三种情况：\n",
    "        - 操作类函数的默认返回值\n",
    "        - 意料之外的缺失值\n",
    "        - 在执行失败时代表错误\n",
    "    - 函数的名称和参数必须表达“结果可能缺失”的意思\n",
    "    - 如果函数执行无法产生结果，调用方也不关心具体原因\n",
    "3. 早返回，多返回\n",
    "### 7.1.3 常用函数模块：functools\n",
    "1. functools.partial()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0c0af21d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "8\n"
     ]
    }
   ],
   "source": [
    "def multiply(x, y):\n",
    "    return x*y\n",
    "import functools\n",
    "double = functools.partial(multiply, 2)\n",
    "print(double(4))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a0cce12",
   "metadata": {},
   "source": [
    "2. functools.lru_cache()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "12e4d32a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Caculating score for class: 100\n",
      "42\n",
      "42\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import functools\n",
    "@functools.lru_cache(maxsize = 1)\n",
    "def caculate_score(class_id):\n",
    "    print(f'Caculating score for class: {class_id}')\n",
    "    time.sleep(60)\n",
    "    return 42\n",
    "print(caculate_score(100))\n",
    "print(caculate_score(100))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61516c47",
   "metadata": {},
   "source": [
    "## 7.2 案例故事\n",
    "- 纯函数：加入输入参数相同， 输出结果也一定相同，是一种无状态的函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc92aa70",
   "metadata": {},
   "outputs": [],
   "source": [
    "def mosaic(s):\n",
    "    \"\"\"把输入字符串替换成等长的星号字符\"\"\"\n",
    "    return '*' * len(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec4337e2",
   "metadata": {},
   "source": [
    "- 函数与状态\n",
    "    - 有一段文字，里面包含各类数字，比如数量、价格等，编写一段代码把文字里的所有数字都用星号替代，实现脱敏的效果\n",
    "    - 原始文本：商店共100个苹果，小明以12元每斤的价格买走了8个\n",
    "    - 目标文本：商店共有*个苹果，小明以*元没斤的价格买走了*个"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1324aaff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "商店共*个苹果，小明以*元每斤的价格买走了*个\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "def mosaic_string(s):\n",
    "    \"\"\"用*替换输入字符串里面的所有连续数字\"\"\"\n",
    "    return re.sub(r'\\d+', '*', s)\n",
    "print(mosaic_string(\"商店共100个苹果，小明以12元每斤的价格买走了8个\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "162af44d",
   "metadata": {},
   "source": [
    "- 进一步修改函数，保留每个被替换数字的原始长度"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2160f02e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "商店共***个苹果，小明以**元每斤的价格买走了*个\n"
     ]
    }
   ],
   "source": [
    "def mosaic_matchobj(matchobj):\n",
    "    \"\"\"将匹配到的模式替换为等长星号字符串\"\"\"\n",
    "    length = len(matchobj.group())\n",
    "    return '*' * length\n",
    "def mosaic_string(s):\n",
    "    \"\"\"用*替换输入字符串里面的所有连续数字\"\"\"\n",
    "    return re.sub(r'\\d+', mosaic_matchobj, s)\n",
    "print(mosaic_string(\"商店共100个苹果，小明以12元每斤的价格买走了8个\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09905cf6",
   "metadata": {},
   "source": [
    "- 给函数加上状态，全局变量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f8cd0594",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "商店共***个苹果，小明以xx元每斤的价格买走了*个\n"
     ]
    }
   ],
   "source": [
    "_mosaic_char_index = 0\n",
    "\n",
    "def mosaic_global_var(matchobj):\n",
    "    \"\"\"\n",
    "    将匹配到的模式转换成其他字符，使用全局变量实现轮换字符效果\n",
    "    \"\"\"\n",
    "    global _mosaic_char_index\n",
    "    mosaic_chars = ['*', 'x']\n",
    "\n",
    "    char = mosaic_chars[_mosaic_char_index]\n",
    "    #递增马赛克字符索引值\n",
    "    _mosaic_char_index = (_mosaic_char_index + 1) % len(mosaic_chars)\n",
    "\n",
    "    length = len(matchobj.group())\n",
    "    return char * length\n",
    "print(re.sub(r'\\d+', mosaic_global_var, '商店共100个苹果，小明以12元每斤的价格买走了8个'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "074946be",
   "metadata": {},
   "source": [
    "- 给函数加上状态，闭包\n",
    "    - 闭包是一种允许函数访问已执行完成的其他函数里的私有变量的技术，是为函数增加状态的另一种方式"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "132977be",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<function counter.<locals>._counter at 0x0000027DFF74CCC0>\n",
      "1\n",
      "2\n"
     ]
    }
   ],
   "source": [
    "def counter():\n",
    "    value = 0\n",
    "    def _counter():\n",
    "        #nonlocal 用来标注变量来自上层作业栈，如不标明，内层函数将无法直接修改外层函数变量\n",
    "        nonlocal value\n",
    "\n",
    "        value+=1\n",
    "        return value\n",
    "    return _counter\n",
    "c = counter()\n",
    "print(c)\n",
    "print(c())\n",
    "print(c())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ac609723",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "商店共***个苹果，小明以xx元每斤的价格买走了*个\n",
      "商店共***个苹果，小明以xx元每斤的价格买走了*个\n"
     ]
    }
   ],
   "source": [
    "def make_cyclic_mosaic():\n",
    "    \"\"\"\n",
    "    将匹配到的模式替换为其他字符，使用闭包实现轮换字符效果\n",
    "    \"\"\"\n",
    "    char_index = 0\n",
    "    mosaic_chars = ['*', 'x']\n",
    "\n",
    "    def _mosaic(matchobj):\n",
    "        nonlocal char_index\n",
    "        char = mosaic_chars[char_index]\n",
    "        char_index = (char_index +1) % len(mosaic_chars)\n",
    "\n",
    "        length = len(matchobj.group())\n",
    "        return char * length\n",
    "    \n",
    "    return _mosaic\n",
    "\n",
    "print(re.sub(r'\\d+', make_cyclic_mosaic(), '商店共100个苹果，小明以12元每斤的价格买走了8个'))\n",
    "print(re.sub(r'\\d+', make_cyclic_mosaic(), '商店共100个苹果，小明以12元每斤的价格买走了8个'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f164516e",
   "metadata": {},
   "source": [
    "5. 给函数加上状态：类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "c2d263f6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "商店共***个苹果，小明以xx元每斤的价格买走了*个\n",
      "商店共xxx个苹果，小明以**元每斤的价格买走了x个\n"
     ]
    }
   ],
   "source": [
    "class CyclicMosaic:\n",
    "    \"\"\"使用轮换的屏幕字符，基于类实现\"\"\"\n",
    "\n",
    "    _chars = ['*', 'x']\n",
    "\n",
    "    def __init__(self):\n",
    "        self._char_index = 0\n",
    "    \n",
    "    def generate(self, matchobj):\n",
    "        char = self._chars[self._char_index]\n",
    "        self._char_index = (self._char_index+1) % len(self._chars)\n",
    "        length = len(matchobj.group())\n",
    "        return char*length\n",
    "CycleMosaic = CyclicMosaic()\n",
    "print(re.sub(r'\\d+', CycleMosaic.generate, '商店共100个苹果，小明以12元每斤的价格买走了8个'))\n",
    "print(re.sub(r'\\d+', CycleMosaic.generate, '商店共100个苹果，小明以12元每斤的价格买走了8个'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7638109",
   "metadata": {},
   "source": [
    "- 小结\n",
    "1. 基于全局变量：\n",
    "- 学习成本低，容易理解\n",
    "- 会增加模块级的全局状态，封闭性和可维护性最差\n",
    "2. 基于函数闭包：\n",
    "- 学习成本适中，可读性较好\n",
    "- 适合用来实现变量较少，较简单的有状态函数\n",
    "3. 基于类来创建封装状态：\n",
    "- 学习成本较高\n",
    "- 当变量较多，行为较复杂时，类代码比闭包代码更易读，也更容易维护"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "124f4717",
   "metadata": {},
   "source": [
    "## 7.3 编程建议\n",
    "### 7.3.1 别写太复杂的函数\n",
    "- 判断标准：\n",
    "  1. 长度\n",
    "    - 大于65行代表太复杂\n",
    "  2. 圈复杂度\n",
    "    - 程序内线性独立路径的数量,超过10代表太复杂"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d96bb85",
   "metadata": {},
   "outputs": [],
   "source": [
    "import bisect\n",
    "def rank(self):\n",
    "    rating_num = float(self.rating)\n",
    "    if rating_num >= 8.5:\n",
    "        return 'S'\n",
    "    elif rating_num >= 8:\n",
    "        return 'A'\n",
    "    elif rating_num >= 7:\n",
    "        return 'B'\n",
    "    elif rating_num >= 6:\n",
    "        return 'C'\n",
    "    else:\n",
    "        return 'D'\n",
    "def rank1(self):\n",
    "    breakpoints = (6, 7, 8, 8.5)\n",
    "    grades = ('D', 'C', 'B', 'A', 'S')\n",
    "    index = bisect.bisect(breakpoints, float(self.rating))\n",
    "    return grades[index]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9dbc792b",
   "metadata": {},
   "source": [
    "### 7.3.2 一个函数之包含一层抽象\n",
    "- 抽象是指在面对复杂事物（或概念）时，主动过滤掉不需要的细节，只关注与当前目的有关的信息\n",
    "\n",
    "“腿真疼啊，找把椅子做吧”，“椅子是一个抽象的概念”：\n",
    "#有一个平坦的表面可以把屁股放上去\n",
    "#离地20到50厘米，能支撑60千克以上的质量\n",
    "假如一个函数内同时包含多个抽象级别的内容，就会引发一系列的问题\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7988d36",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "from json.decoder import JSONDecodeError\n",
    "\n",
    "import requests\n",
    "from requests.exceptions import HTTPError\n",
    "\n",
    "ITUNES_API_ENDPOINT = 'https://itunes.apple.com/search'\n",
    "\n",
    "class GetFirstAlbumError(Exception):\n",
    "    \"\"\"获取第一张专辑失败\"\"\"\n",
    "\n",
    "class QueryAlbumsError(Exception):\n",
    "    \"\"\"获取专辑列表失败\"\"\"\n",
    "\n",
    "def command_first_album():\n",
    "    \"\"\"通过输入参数查找并打印歌手的第一张专辑的信息\"\"\"\n",
    "    if not len(sys.argv) == 2:\n",
    "        print(f'usage: python {sys.argv[0]} {{SEARCH_TERM}}')\n",
    "        sys.exit(1)\n",
    "    artist = sys.argv[1]\n",
    "    try:\n",
    "        album = get_first_album(artist)\n",
    "    except  GetFirstAlbumError as e:\n",
    "        print(f\"error: {e}\", file = sys.stderr)\n",
    "        sys.exit(2)\n",
    "    \n",
    "    print(f\"{artist}'s first album:\")\n",
    "    print(f\"    *name: {album['name']}\")\n",
    "    print(f\"    *Genre: {album['genre_name']}\")\n",
    "    print(f\"    *Released at: {album['release_date']}\")\n",
    "\n",
    "def get_first_album(artist):\n",
    "    \"\"\"根据专辑列表获取第一张专辑\n",
    "    :param artist:歌手名称\n",
    "    :return:第一张专辑\n",
    "    :raise:获取失败时抛出GetFirstAlbumError\n",
    "    \"\"\"\n",
    "    try:\n",
    "        albums = query_all_albums(artist)\n",
    "    except QueryAlbumsError as e:\n",
    "        raise GetFirstAlbumError(str(e))\n",
    "    \n",
    "    sorted_albums = sorted(albums, key = lambda item: item['releaseDate'])\n",
    "    first_album = sorted_albums[0]\n",
    "    #去除发布日期里的小时与分钟信息\n",
    "    release_data = first_album['releaseDate'].split('T')[0]\n",
    "    return {\n",
    "        'name': first_album['collectionName'],\n",
    "        'genre_name': first_album['primaryGenreName'],\n",
    "        'release_data': release_data\n",
    "    }\n",
    "\n",
    "def query_all_albums(artist):\n",
    "    \"\"\"根据歌手搜索所有专辑列表\n",
    "    :param artist:歌手名字\n",
    "    ：return:专辑列表, List[Dict]\n",
    "    ：raise:获取失败时抛出GetAlbumsError\n",
    "    \"\"\"\n",
    "    resp = requests.get(\n",
    "        ITUNES_API_ENDPOINT,\n",
    "        {\n",
    "            'term': artist,\n",
    "            'media':'music',\n",
    "            'entity':'album',\n",
    "            'attribute': 'artistTerm',\n",
    "            'limit': 200\n",
    "        },\n",
    "    )\n",
    "    try:\n",
    "        resp.raise_for_status()\n",
    "    except HTTPError as e:\n",
    "        raise QueryAlbumsError(f'failed to call iTunes API, {e}')\n",
    "    try:\n",
    "        albums = resp.json()['results']\n",
    "    except JSONDecodeError:\n",
    "        raise QueryAlbumsError('response is not valid JSON format')\n",
    "    if not albums:\n",
    "        raise QueryAlbumsError(f'no albums found for artist \"{artist}\"')\n",
    "    return albums\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    command_first_album()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e119860",
   "metadata": {},
   "source": [
    "### 7.3.3 优先使用列表推导式\n",
    "1. map(func, iterable):遍历并执行func获取结果，迭代返回新结果\n",
    "2. filter(func, iterable)：遍历并使用func测试成员，仅当结果为真时返回\n",
    "3. lambda:定义一个一次性使用的匿名函数\n",
    "### 7.3.4 你没有那么需要lambda\n",
    "    - 匿名函数\n",
    "    - peration模块\n",
    "### 7.3.5 了解递归的局限性"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "865a97b5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n"
     ]
    }
   ],
   "source": [
    "def fib(n):\n",
    "    if n<2:\n",
    "        return n\n",
    "    return fib(n-1) + fib(n-2)\n",
    "\n",
    "print([fib(i) for i in range(10)])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1d01327",
   "metadata": {},
   "source": [
    "- 最大递归深度限制有python在语言层面上设置\n",
    "- 尾调用优化技术\n",
    "- 加上缓存优化递归性能"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
